iT邦幫忙

2026 iThome 鐵人賽

DAY 22
0
Software Development

文藝復興:這段程式碼,好像有點味道系列 第 22

Day 22|只會被人擠顏料,自己不會調色的調色盤:純資料類別 (Data Class)

  • 分享至 

  • xImage
  •  

畫室裡有一種工具,叫調色盤

它的角色,就是被動地裝顏料
畫家把紅色、黃色擠上去,自己動手調出想要的橘色

調色盤本身不會調色,這很正常,它本來就只是個容器

但如果一個「應該懂得調色」的角色,卻只會被動地被人擠顏料、自己什麼判斷都不做
問題就不在調色盤,是有人把該做判斷的工作,丟給了不該做判斷的東西

一個只有 getter/setter 的庫存資料

專案裡有一個庫存記錄類別:

public class ProductStock
{
    public string Sku { get; set; }
    public int Quantity { get; set; }
    public decimal UnitPrice { get; set; }
}

單看這個類別,完全看不出它在系統裡扮演什麼角色

它只是一袋被公開的欄位,任何人都可以把它塞滿、掏空、改到面目全非,ProductStock 自己完全不會有意見

判斷邏輯,全部外包給別人

真正「懂得」這筆庫存資料該怎麼用的邏輯,被放進了另一個類別:

public class InventoryValuationService
{
    public decimal CalculateLineValue(ProductStock stock)
    {
        return stock.Quantity * stock.UnitPrice;
    }

    public bool IsLowStock(ProductStock stock)
    {
        return stock.Quantity < 10;
    }
}

這兩個方法,做的事情都只跟 ProductStock 自己的欄位有關,卻被寫在別的類別裡

隨著專案長大,越來越多地方需要「算庫存價值」「判斷是否低庫存」

  • 報表模組寫了一份自己的計算邏輯
  • 補貨提醒功能,又寫了一份判斷低庫存的邏輯,門檻設成 < 5,跟 InventoryValuationService< 10 對不上
  • 沒有人記得,這些邏輯理論上該只有一個家

ProductStock 就像那塊調色盤,誰都能往上面擠顏料
卻沒有人問過它:「這幾種顏色,你覺得該怎麼調?」

它從來沒有機會回答,因為它根本沒有被賦予判斷的能力

把判斷力,還給資料自己

解法是搬移方法 (Move Method):把只用到 ProductStock 自身欄位的邏輯,搬回 ProductStock 內部

public class ProductStock
{
    public string Sku { get; }
    public int Quantity { get; }
    public decimal UnitPrice { get; }

    public ProductStock(string sku, int quantity, decimal unitPrice)
    {
        Sku = sku;
        Quantity = quantity;
        UnitPrice = unitPrice;
    }

    public decimal CalculateValue() => Quantity * UnitPrice;

    public bool IsLowStock() => Quantity < 10;
}

欄位也順手改成唯讀 (get 沒有 set),只能透過建構子設定一次

InventoryValuationService 不再需要存在
它原本存在的唯一理由,是幫 ProductStock 做它自己該做的判斷

報表模組、補貨提醒功能,全部改成直接呼叫 stock.CalculateValue()stock.IsLowStock()
低庫存的門檻,終於只剩一個地方能改

不是所有「只有欄位」的類別都有罪

節制美學提醒我們:判斷一個東西該不該留,要看它存在的角色,不是看它的外型

有幾種「純資料類別」的外型,其實是合理的:

  • DTO(資料傳輸物件):專門用來跨越邊界搬運資料,本來就不該帶行為,帶了行為反而是責任不清
  • 資料庫實體(Entity):對映資料表結構的類別,通常也是刻意保持單純
  • 不可變的值物件:像 Day 05 用過的 EmailProductId,雖然欄位不多,但它們有自己的驗證邏輯與行為,不是純資料類別,是被誤會成純資料類別的值物件

真正該被盯上的,是那種「明明有專屬於自己的判斷邏輯,卻被迫外包給別人」的資料容器

怎麼判斷,行為該不該搬回來

  • 這個方法,用到的欄位,是不是全部來自同一個資料類別?
  • 這段邏輯,如果搬回資料類別內部,讀者理解起來,會不會更直覺?
  • 這個類別,除了被塞資料、被掏資料,有沒有機會自己對外回答一個問題

如果答案都是肯定的,這份行為,本來就該屬於它

自我檢查清單

  1. 這個類別,除了 getter 跟 setter,還有沒有屬於自己的方法?
  2. 有沒有別的類別,寫了只依賴這個資料類別欄位的邏輯?
  3. 這個類別的欄位,是不是可以被任何人隨意修改,而它自己完全不會檢查?
  4. 這個類別,是刻意設計成 DTO/Entity,還是不小心變成了空殼?
  5. 如果把相關的判斷邏輯搬回這個類別,程式碼會不會變得更容易讀懂?

明日預告

明天我們看畫室角落一個更明顯的贅肉:一段沒有人再呼叫、卻也沒有人敢動手拆掉的舊程式碼

模組四第五站:無用的程式碼(Dead Code)


上一篇
Day 21|領了畫布錢,卻什麼都沒畫的學徒:懶惰的類別 (Lazy Class)
下一篇
Day 23|畫室角落,早就沒人用的舊畫架:無用的程式碼 (Dead Code)
系列文
文藝復興:這段程式碼,好像有點味道27
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言